#!/bin/sh

## CryptShotR

## Open and mount a LUKs volume, locally or remotely, before performing a
# backup with rsnapshot.
#
# Author:     demure demeanor
# Repository: https://notabug.org/demure/scripts
# Website:    http://demu.red/
# Blog Post:  http://demu.red/

## This is based on cryptshot by:
# Author:   Pig Monkey (pm@pig-monkey.com)
# Website:  https://github.com/pigmonkey/backups

## Modified to support a GPG encrypted LUKs key file.
# This can backup to the LUKs container either locally, or over the network
# to server where the LUKs container is connected. Supports a flag to
# suppress non-configuration related errors. The script can optionally
# support ssh-agent. GPG decryption relies on the gpg-agnet caching, which
# I frequently refresh for pulling emails in mutt.
#
# It will check if the volume with configured UUID exists locally, and if
# not will check remotely for the volume. If the volume is found it will be
# treated as a LUKs volume and decrypted with the given GPG encrypted LUKs
# key file, and then mounted. The script then runs rsnapshot. After the
# backup is complete the volume is unmounted and closed. Can optionally
# delete the mount point for cleanup.
# 
# This script requires a LUKs+dmcrypt container, as well as a GPG encrypted
# LUKs key file. The container allows for secure storage of backups.
# The encrypted key file protects the key from compromise.
#
# The script should be called with your configured rsnapshot intervals.
# Optionally, '-q' or '-quiet' can be used as the first augment, to silence
# non-configuration errors.

### Conf ### {{{
## Define the UUID of the backup volume.
UUID=""

## Define the location of the LUKS key file.
KEYFILE=""

## Define gpg key, used to decrypt LUKS key file.
GPGPRINT=""

## Define the mount point for the backup volume.
## This will be created if it does not already exist.
MOUNTPOINT="/mnt/$UUID"

## Any non-zero value here will caused the mount point to be deleted after the
## volume is unmounted.
REMOVEMOUNT=0

## Define the location of rsnapshot.
RSNAPSHOT=""
### End Conf ### }}}

### Remote Conf ### {{{
## Enable Remote backups
RE_ON=1

## Enable ssh agent
# This is assuming that your key is already sourced like:
# https://notabug.org/demure/dotfiles/src/master/subbash/sshagent
RE_AGENT=0

## Define home routers MAC addr.
RE_HOME=""

## Define remote address, for testing.
RE_HOST=""

## Define remote user
RE_USER=""
### End Remote Conf ### }}}

### Startup Tests ### {{{
## Exit if no volume is specified.
if [ "$UUID" = "" ]; then
	echo 'No volume specified.'
	exit 78
fi

## Exit if no key file is specified.
if [ "$KEYFILE" = "" ]; then
	echo 'No key file specified.'
	exit 78
fi

## Exit if no gpg finger print is specified.
if [ "$GPGPRINT" = "" ]; then
	echo 'No GPG finger print specified.'
	exit 78
fi

## Exit if no mount point is specified.
if [ "$MOUNTPOINT" = "" ]; then
	echo 'No mount point specified.'
	exit 78
fi

## Exit if no interval was specified.
## Tests about this line should not be effected by Quiet flag
## Why? Well, it's enacted here, and missing configuration is BAD.
if [ "$1" = "" ]; then
	echo "No interval specified."
	exit 64
  elif [ "$1" = "-q" ] || [ "$1" = "--quiet" ]; then
	## set quiet mode code here.
	## NOTE: Make quiet mode not effect missing settings or interval.
	QUIET=1

	if [ "$2" = "" ]; then
		echo "No interval specified."
		exit 64
	  else
		interval=$2
	fi
  else
	interval=$1
	QUIET=0
fi
### End Startup Tests ### }}}

### Remote Startup Tests ### {{{
if [ "$RE_ON" = "1" ]; then
	if [ "$RE_HOME" = "" ]; then
		echo "No local network router MAC specified."
		exit 73
	fi

	if [ "$RE_HOST" = "" ]; then
		echo "No remote host specified."
		exit 73
	fi

	if [ "$RE_USER" = "" ]; then
		echo "No remote user specified."
		exit 73
	fi
fi
### End Remote Startup Tests ### }}}

### Prep Work ### {{{
## Build the reference to the volume.
volume="/dev/disk/by-uuid/$UUID"

## Create a unique name for the LUKS mapping.
name="crypt-$UUID"

## If the mount point does not exist, create it.
if [ ! -d "$MOUNTPOINT" ]; then
	mkdir $MOUNTPOINT
	# Exit if the mount point was not created.
	if [ $? -ne 0 ]; then
		echo "Failed to create mount point."
		exit 73
	fi
fi

	### GPG Test ### {{{
	## Check that GPG key is cached in gpg-agent
	## First see if SUDO_UID exists.
	# This is needed as cryptshotr-cron -> cryptshotr results in using SUDO_UID
	# but cryptshot run manually has UID
	# http://demu.red/blog/2016/08/how-to-check-if-your-gpg-key-is-in-cache-part-2/
	if [ -z "${SUDO_UID}" ]; then
		gpg_socket="/run/user/${UID}/gnupg/S.gpg-agent"
	  else
		gpg_socket="/run/user/${SUDO_UID}/gnupg/S.gpg-agent"
	fi

	## Test if cached
	if [ -e ${gpg_socket} ]; then
		gpg_test=$(gpg-connect-agent -S ${gpg_socket} 'keyinfo --list' /bye 2>/dev/null | awk 'BEGIN{CACHED=0} /^S/ {if($7==1){CACHED=1}} END{print CACHED}')
	  else
		gpg_test=$(gpg-connect-agent 'keyinfo --list' /bye 2>/dev/null | awk 'BEGIN{CACHED=0} /^S/ {if($7==1){CACHED=1}} END{print CACHED}')
	fi

	## Finally, the graceful failure.
	if [ $gpg_test = "0" ]; then
		if [ "$QUIET" = 0 ]; then
			echo 'GPG key is not cached.'
		fi
		exit 78
	fi
	### End GPG Test ### }}}

## Load ssh-agent, if enabled.
if [ "$RE_AGENT" = 1 ]; then
	ssh_env="$HOME/.ssh/environment"

	if [ -f ${ssh_env} ]; then
		. "${ssh_env}" >/dev/null
		ps -ef | grep ${SSH_AGENT_PID} | grep ssh-agent$ >/dev/null || {
			echo "PID bad?";
		}
	  else
		echo "ENV file bad?"
	fi
fi

## Set the default exit code to 0.
exitcode=0
### End Prep Work ### }}}

## Continue if the volume exists.
if [ -e $volume ]; then
	### Local Backup ### {{{
	## Attempt to open the LUKS volume.
	gpg2 --no-permission-warning --no-tty -qd $KEYFILE | cryptsetup luksOpen --key-file=- $volume $name
	## If the volume was decrypted, mount it. 
	if [ $? -eq 0 ]; then
		mount /dev/mapper/$name $MOUNTPOINT
		## If the volume was mounted, run the backup.
		if [ $? -eq 0 ]; then
			## Run rsnapshot
			$RSNAPSHOT $interval
			## Unmount the volume
			umount $MOUNTPOINT
			## If the volume was unmounted and the user has requested that the
			## mount point be removed, remove it.
			if [ $? -eq 0 -a $REMOVEMOUNT -ne 0 ]; then
				rmdir $MOUNTPOINT
			fi
		  else
			exitcode=$?
			if [ $"$QUIET" = 0 ]; then
				echo "Failed to mount $volume at $MOUNTPOINT."
			fi
		fi
		## Close the LUKS volume.
		cryptsetup luksClose $name
	  else
		exitcode=$?
		if [ $"$QUIET" = 0 ]; then
			echo "Failed to open $volume with key $KEYFILE."
		fi
	fi
	### End Local Backup ### }}}
  else
	### Remote Backup ### {{{
	if [ $RE_ON -ne 0 ]; then
		re_addr="${RE_USER}@${RE_HOST}"
		## Get MAC of router
		check_mac="$(iwgetid --ap -r)"
		if [ "$RE_HOME" = "$check_mac" ]; then
			## Check that host is up
			ping -c 3 $RE_HOST 1>/dev/null
			if [ $? = 0 ]; then
				## Check if drive connected
				check_volume=$(ssh $re_addr "test -e $volume && echo 1 || echo 0" 2>/dev/null)
				if [ "$check_volume" = 1 ]; then
					## Make MOUNTPOINT if not present
					ssh $re_addr "test -e $MOUNTPOINT || sudo mkdir $MOUNTPOINT"
					if [ $? = 0 ]; then
						## Open LUKs container
						gpg2 --no-permission-warning --no-tty -qd $KEYFILE | ssh $re_addr "sudo cryptsetup luksOpen --key-file=- $volume $name"
						if [ $? = 0 ]; then
							## Mount LUKs container
							ssh $re_addr "sudo mount /dev/mapper/$name $MOUNTPOINT"
							if [ $? = 0 ]; then
								## Run rsnapshot
								ssh $re_addr "sudo $RSNAPSHOT $interval"
								## Unmount LUKs container
								ssh $re_addr "sudo umount $MOUNTPOINT"
								## If the volume was unmounted and the user has requested that the
								## mount point be removed, remove it.
								if [ $? -eq 0 -a $REMOVEMOUNT -ne 0 ]; then
									ssh $re_addr "sudo rmdir $MOUNTPOINT"
									rmdir $MOUNTPOINT
								fi
							  else
								exitcode=$?
								if [ $"$QUIET" = 0 ]; then
									echo "Failed to mount remote $volume at $MOUNTPOINT."
								fi
							fi
						  else
							exitcode=$?
							if [ $"$QUIET" = 0 ]; then
								echo "Failed to open remote $volume with key $KEYFILE."
							fi
						fi
						## Close LUKs Container
						ssh $re_addr "sudo cryptsetup luksClose $name"
					  else
						exitcode=66
						if [ $"$QUIET" = 0 ]; then
							echo "Could not make remote $MOUNTPOINT."
						fi
					fi
				  else
					exitcode=66
					if [ $"$QUIET" = 0 ]; then
						echo "Remote volume $UUID not found."
					fi
				fi
			  else
				exitcode=66
				if [ $"$QUIET" = 0 ]; then
					echo "Host not found."
				fi
			fi
		  else
			exitcode=66
			if [ $"$QUIET" = 0 ]; then
				echo "Not on home network."
			fi
		fi
	  else
		exitcode=33
		if [ $"$QUIET" = 0 ]; then
			echo "Volume $UUID not found."
		fi
	fi
	### End Remote Backup ### }}}
fi

exit $exitcode
